﻿//------------------------------------------------------------------------------
//  Namespace: FruitVentDesign.Controls
//  
//  Function： N/A
//  Name： PromptInput
//  
//  Ver       Time                     Author
//  0.10      2021/6/24 17:02:56      FruitVent
//
//  此代码版权归作者本人FruitVent所有
//  源代码使用协议遵循本仓库的开源协议及附加协议，若本仓库没有设置，则按MIT开源协议授权
//  CSDN博客：https://blog.csdn.net/weixin_39552347
//  源代码仓库：https://gitee.com/fruitvent
//  感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace FruitVentDesign.Controls
{
    public class PromptInput : Input
    {
        static PromptInput()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(PromptInput), new FrameworkPropertyMetadata(typeof(PromptInput)));
        }

        /// <summary>
        /// ItemTemplateProperty
        /// </summary>
        public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
            "ItemTemplate",
            typeof(DataTemplate),
            typeof(PromptInput)
            );
        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }

        /// <summary>
        /// ItemsSourcePorperty
        /// </summary>
        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
            "ItemsSource",
            typeof(IEnumerable),
            typeof(PromptInput)
            );
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        /// <summary>
        /// ItemDisplayPropertyProperty
        /// </summary>
        public static readonly DependencyProperty ItemDisplayPropertyProperty = DependencyProperty.Register(
            "ItemDisplayProperty",
            typeof(string),
            typeof(PromptInput)
            );
        public string ItemDisplayProperty
        {
            get { return (string)GetValue(ItemDisplayPropertyProperty); }
            set { SetValue(ItemDisplayPropertyProperty, value); }
        }

        #region event
        public event EventHandler EndEdit;

        public static readonly DependencyProperty EndEditCommandProperty = DependencyProperty.Register(
            "EndEditCommand",
            typeof(ICommand),
            typeof(PromptInput)
            );

        public ICommand EndEditCommand
        {
            get { return (ICommand)GetValue(EndEditCommandProperty); }
            set { SetValue(EndEditCommandProperty, value); }
        }

        public static readonly DependencyProperty EndEditCommandParameterProperty = DependencyProperty.Register(
            "EndEditCommandParameter",
            typeof(object),
            typeof(PromptInput)
            );

        public object EndEditCommandParameter
        {
            get { return GetValue(EndEditCommandParameterProperty); }
            set { SetValue(EndEditCommandParameterProperty, value); }
        }

        /// <summary>
        /// fire event and command
        /// </summary>
        private void OnEndEdit()
        {
            EndEdit?.Invoke(this, EventArgs.Empty);

            if (EndEditCommand != null && EndEditCommand.CanExecute(EndEditCommandParameter))
            {
                EndEditCommand.Execute(EndEditCommandParameter);
            }
        }
        #endregion

        public int MaxMatchNum { get; set; } = 10;
        private ListBox Prompt_ListBox;
        private Popup Prompt_Popup;

        public PromptInput()
        {
            GotFocus += PromptInput_GotFocus;
            LostFocus += PromptInput_LostFocus;
            KeyUp += PromptInput_KeyUp;
        }

        /// <summary>
        /// Listen to keyboard events
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PromptInput_KeyUp(object sender, KeyEventArgs e)
        {
            if(e.Key == Key.Enter)
            {
                Execute();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void Execute()
        {
            if (Prompt_Popup.IsOpen)
            {
                Prompt_Popup.IsOpen = false;
            }
            if (!string.IsNullOrWhiteSpace(Text))
            {
                bool flag = false;
                foreach (object item in ItemsSource)
                {
                    string displayText = GetDisplayText(item);
                    if (displayText.Contains(Text))
                    {
                        _suspendTextChanged = true;
                        Text = GetDisplayText(item);
                        _suspendTextChanged = false;
                        flag = true;
                        break;
                    }
                }
                if (!flag)
                {
                    _suspendTextChanged = true;
                    Text = null;
                    _suspendTextChanged = false;
                }
            }
            else
            {
                _suspendTextChanged = true;
                Text = null;
                _suspendTextChanged = false;
            }
            OnEndEdit();
        }

        /// <summary>
        /// when PromptInput lost focus
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PromptInput_LostFocus(object sender, RoutedEventArgs e)
        {
            _suspendTextChanged = true;
        }

        /// <summary>
        /// when PromptInput get focus
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PromptInput_GotFocus(object sender, RoutedEventArgs e)
        {
            _suspendTextChanged = false;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            Prompt_ListBox = Template.FindName("Prompt_ListBox", this) as ListBox;//列表
            Prompt_Popup = Template.FindName("Prompt_Popup", this) as Popup;
            Prompt_ListBox.SelectionChanged += Prompt_ListBox_SelectionChanged;
        }

        private bool _suspendTextChanged = true;
        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            if (_suspendTextChanged) { return; }

            // if max match num is less than 1
            // then no need to popup
            if (MaxMatchNum < 1) { return; }

            // do filter
            IEnumerable filteredSource = DoFilter(Text, MaxMatchNum);

            // suspend handler for selection changed
            _suspendSelectionChanged = true;
            Prompt_ListBox.SelectedIndex = -1;
            Prompt_ListBox.ItemsSource = filteredSource;
            _suspendSelectionChanged = false;

            if (!Prompt_Popup.IsOpen)
            {
                // open popup and set focus.
                Prompt_Popup.IsOpen = true;
                FocusManager.SetFocusedElement(this, Prompt_Popup);
            }
            base.OnTextChanged(e);
        }

        private bool _suspendSelectionChanged = false;
        private void Prompt_ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (_suspendSelectionChanged) { return; }

            Prompt_Popup.IsOpen = false;

            // suspend handler for text changed.
            // just set the value of Text
            _suspendTextChanged = true;
            Text = GetDisplayText(Prompt_ListBox.SelectedItem);
            _suspendTextChanged = false;
            Focus();
        }

        /// <summary>
        /// do filter in ItemsSource
        /// </summary>
        /// <param name="text"></param>
        /// <param name="maxMatchNum"></param>
        /// <returns></returns>
        private IEnumerable DoFilter(string text, int maxMatchNum)
        {
            if (ItemsSource == null) return new List<object>();

            if (string.IsNullOrWhiteSpace(text))
            {
                List<object> matchedItems = new List<object>();
                int num = 0;
                foreach (var item in ItemsSource)
                {
                    matchedItems.Add(item);
                    num += 1;
                    if (num >= maxMatchNum) { break; }
                }

                return matchedItems;
            }
            else
            {
                List<object> matchedItems = new List<object>();
                int num = 0;
                foreach (object item in ItemsSource)
                {
                    // using Equals(string) to do filter
                    string displayText = GetDisplayText(item);
                    if (displayText.Contains(text))
                    {
                        matchedItems.Add(item);
                        num += 1;
                        if (num >= maxMatchNum) { break; }
                    }
                }

                return matchedItems;
            }
        }

        /// <summary>
        /// get display text of specified object according ItemDisplayProperty
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private string GetDisplayText(object obj)
        {
            if (obj == null) { return ""; }

            if (string.IsNullOrEmpty(ItemDisplayProperty))
            {
                // by default, return obj.ToString()
                return obj.ToString();
            }

            Type type = obj.GetType();
            var displayPropertyDes = type.GetProperty(ItemDisplayProperty);
            if (displayPropertyDes == null)
            {
                return $"cannot find property '{ItemDisplayProperty}'.";
            }
            object displayValue = displayPropertyDes.GetValue(obj);
            return displayValue == null ? "" : displayValue.ToString();
        }
    }
}
